﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Arroguella.Utilities;
using Microsoft.Xna.Framework;
using System.Diagnostics;
using Arroguella.Entities;

namespace Arroguella.Tiles
{
    public class DungeonMapGenerator : MapGenerator
    {
        #region Constructor

        public DungeonMapGenerator(Dictionary<Vector2, Tile> tileMap, Guid seed) : base(tileMap, seed)
        {
            rooms = new List<Vector4>();
            InitializeEntityLists();
        }

        #endregion

        #region Events
        #endregion

        #region Methods

        private void InitializeEntityLists()
        {
            List<string> money = new List<string> { "Ruby", "Emerald", "Sapphire", "Diamond", "Topaz", "Opal", "Quartz", "Garnet", "Jade", "Copper Coins", "Silver Coins", "Gold Coins", "Platinum Coins", "Human Teeth" };
            MoneyChooser = new RandomStringChooser(money);

            List<string> monsters = new List<string>{"Bat", "Rat", "Sentient Blueberry Pie", "Ghost", "Cave Kitten"};
            MonsterChooser = new RandomStringChooser(monsters);

            List<string> items = new List<string> {"Sword", "Copper Axe", "Club", "Helmet", "Shield", "Boots", "Guantlets", "Cloak", "Hairball"};
            ItemChooser = new RandomStringChooser(items);
        }

        /// <summary>
        /// Generates a random gem
        /// </summary>
        private MoneyEntity GenerateGem()
        {
            string money = MoneyChooser.Choose();
            switch (money)
            {
                case "Ruby": return new MoneyEntity(money, '*', Color.Red, 60f);
                case "Emerald": return new MoneyEntity(money, '*', Color.Green, 55f);
                case "Sapphire": return new MoneyEntity(money, '*', Color.Blue, 65f);
                case "Diamond": return new MoneyEntity(money, '*', Color.White, 125f);
                case "Topaz": return new MoneyEntity(money, '*', Color.SkyBlue, 25f);
                case "Opal": return new MoneyEntity(money, '*', Color.Gold, 35f);
                case "Quartz": return new MoneyEntity(money, '*', Color.WhiteSmoke, 0.5f);
                case "Garnet": return new MoneyEntity(money, '*', Color.DarkRed, 0.25f);
                case "Jade": return new MoneyEntity(money, '*', Color.SeaGreen, 12.5f);
                case "Copper Coins": return new MoneyEntity(money, '*', Color.DarkOrange, 0.10f);
                case "Silver Coins": return new MoneyEntity(money, '*', Color.LightGray, 0.80f);
                case "Gold Coins": return new MoneyEntity(money, '*', Color.Goldenrod, 15.0f);
                case "Platinum Coins": return new MoneyEntity(money, '*', Color.Silver, 90.0f);
                case "Human Teeth": return new MoneyEntity(money, '"', Color.Wheat, 0.01f);
            }

            return new MoneyEntity("Unidentified Gem", '*', Color.Yellow, 0.5f);
        }

        /// <summary>
        /// Generates a random item
        /// </summary>
        /// <returns></returns>
        private EquipmentEntity GenerateItem()
        {
            string item = ItemChooser.Choose();
            switch(item)
            {
                case "Sword": return new EquipmentEntity(item, 'l', Color.DarkGray, new List<string> {"Main Hand", "Off Hand"},
                    1, 8, 0, 0, 0,
                    "A common sword, made with low-grade steel. Swords like these are cheaply produced by apprentice blacksmiths and are generally used for practice as opposed to full combat.", 5.0f);
                case "Copper Axe": return new EquipmentEntity(item, 'p', Color.Orange, new List<string>{"Main Hand", "Off Hand"},
                    2, 6, 0, 0, 0,
                    "A one-handed axe, with the blade made of copper. Cheaper than steel, but can dull rather easily.", 4.5f);
                case "Club": return new EquipmentEntity(item, '/', Color.Brown, new List<string> { "Main Hand", "Off Hand" },
                    1, 6, 0, 0, 0,
                    "A wooden club, the favorite tool of town watchmen everywhere.", 1.2f);
                case "Helmet": return new EquipmentEntity(item, 'm', Color.Brown, new List<string> {"Head" },
                    0, 0, 0, 0, 1,
                    "A simple helmet made of thick leather. Worn by cautious horse jockeys and low-grade militia, usually.", 0.75f);
                case "Shield": return new EquipmentEntity(item, 'D', Color.LightYellow, new List<string> { "Off Hand", "Main Hand" },
                    0, 0, 0, 0, 3,
                    "A oval wooden shield, made of sturdy oak.", 3.8f);
                case "Boots": return new EquipmentEntity(item, 'b', Color.Brown, new List<string> {"Feet", "Hands"}, "Simple leather boots, stopping at the ankle. More for walking about town than hiking, really.", 1.6f);
                case "Guantlets": return new EquipmentEntity(item, '"', Color.DarkGray, new List<string> {"Hands", "Feet" },
                    0, 0, 0, 1, 1,
                    "Guantlets traditionally are pieces of full plate armor that protect the hands and wrists, however you may feel free to wear them as you please.", 2.65f);
                case "Cloak": return new EquipmentEntity(item, 'A', Color.Maroon, new List<string> {"Back", "Torso", "Head" }, "This is a common cotton cloak, meant to protect from light weather or show off your thrifty sense of fashion.", 1.25f);
                case "Hairball": return new EquipmentEntity(item, '*', Color.SaddleBrown, new List<string> {"Main Hand", "Off Hand", "Ear"}, "Eeeww... this disgusting lump of mucus, hair, vomit, and spit is larger than your fist! Why would you pick it up? WHY?!", 1.0f);
            }

            return new EquipmentEntity(item, '*', Color.SaddleBrown, new List<string> {"Main Hand", "Off Hand", "Ear"}, "Eeeww... this disgusting lump of mucus, hair, vomit, and spit is larger than your fist! Why would you pick it up? WHY?!", 1.0f);
        }

        /// <summary>
        /// Generates a random Monster
        /// </summary>
        private CreatureEntity GenerateMonster(int level)
        {
            string monster = MonsterChooser.Choose();
            switch(monster)
            {
                case "Bat": return MakeWanderingMonster(monster, 'b', Color.Black, level);
                case "Rat": return MakeWanderingMonster(monster, 'r', Color.LightGray, level);
                case "Sentient Blueberry Pie": return MakeWanderingMonster(monster, 'p', Color.Blue, level);
                case "Ghost": return MakeWanderingMonster(monster, 'g', Color.Gray, level);
                case "Cave Kitten": return MakeWanderingMonster(monster, 'k', Color.SaddleBrown, level);
            }

            return new CreatureEntity("Bug", '~', Color.Yellow, 1);
        }

        /// <summary>
        /// Makes a new CreatureEntity with the SimpleWandinerAiComponent
        /// </summary>
        private CreatureEntity MakeWanderingMonster(string name, char symbol, Color color, int level)
        {
            CreatureEntity monster = new CreatureEntity(name, symbol, color, level);
            monster.AddComponent(new SimpleWanderingAiComponent());
            return monster;
        }
        
        /// <summary>
        /// Chooses a random tile coordinate from within a room
        /// </summary>
        private Vector2 ChooseRandomRoomCoordiantes(Rectangle room)
        {
            int x = RandomInt(room.X + 1, room.X + room.Width - 1);
            int y = RandomInt(room.Y + 1, room.Y + room.Height - 1);

            Debug.Assert(room.Contains(x, y));

            return new Vector2(x, y);
        }

        public override void MakeMap(int level)
        {
            BlockAll();

            for (int i = 0; i < MAX_ROOMS; i++)
            {
                // Random width and height
                int w = RandomInt(MIN_ROOM_SIZE, MAX_ROOM_SIZE);
                int h = RandomInt(MIN_ROOM_SIZE, MAX_ROOM_SIZE);

                // Random position without going out of the boundaries of the map
                int x = RandomInt(0, NumberOfTileColumns - w - 1);
                int y = RandomInt(0, NumberOfTileRows - h - 1);

                Vector4 newRoom = new Vector4(x, y, w, h);

                // Run through the other rooms and see if they intersect with this one
                bool failed = false;
                foreach (Vector4 otherRoom in rooms)
                {
                    if (Intersects(newRoom, otherRoom))
                    {
                        failed = true;
                        break;
                    }
                }

                // If the room is valid, we can carve it out
                if (!failed)
                {
                    // This means there are no intersections, so this room is valid
                    CarveOutRoom(newRoom);

                    Rectangle r = new Rectangle((int)newRoom.X, (int)newRoom.Y, (int)newRoom.Z, (int)newRoom.W);

                    // Center coordinates of new room, will be useful later.
                    Vector2 newRoomCenter = CenterOf(newRoom);

                    //
                    // Special case for the first room: player starts here!
                    //
                    if (numberOfRooms == 0)
                    {
                        Tile startTile = TileMap[newRoomCenter];
                        startTile.IsPlayerOccupied = true;
                        startTile.PlayerStart = true;
                        startTile.AddEnvironmentalEntity(UpStairs.CreateStairs());
                    }
                    else // All rooms after the first
                    {
                        // We need to connect it to the previous room with a tunnel

                        // Center coordinates of previous room
                        Vector2 prevRoomCenter = CenterOf(rooms[numberOfRooms - 1]);

                        // Determine how we do tunnels
                        #region Tunnel Generation
                        int choice = RandomInt(1, 5);
                        switch (choice)
                        {
                            default:
                            case 1:
                                {
                                    Vector3 hTunnel = new Vector3(prevRoomCenter.X, newRoomCenter.X, prevRoomCenter.Y);
                                    CarveHorizontalTunnel(hTunnel);
                                    Vector3 vTunnel = new Vector3(prevRoomCenter.Y, newRoomCenter.Y, newRoomCenter.X);
                                    CarveVerticalTunnel(vTunnel);
                                    break;
                                }
                            case 2:
                                {
                                    Vector3 vTunnel = new Vector3(prevRoomCenter.Y, newRoomCenter.Y, newRoomCenter.X);
                                    CarveVerticalTunnel(vTunnel);
                                    Vector3 hTunnel = new Vector3(prevRoomCenter.X, newRoomCenter.X, prevRoomCenter.Y);
                                    CarveHorizontalTunnel(hTunnel);  
                                    break;
                                }
                            case 3:
                                {
                                    Vector3 hTunnel = new Vector3(prevRoomCenter.X, newRoomCenter.X, prevRoomCenter.Y);
                                    CarveHorizontalTunnel(hTunnel);
                                    hTunnel = new Vector3(prevRoomCenter.X, newRoomCenter.X, prevRoomCenter.Y - 1);
                                    CarveHorizontalTunnel(hTunnel);
                                    Vector3 vTunnel = new Vector3(prevRoomCenter.Y, newRoomCenter.Y, newRoomCenter.X);
                                    CarveVerticalTunnel(vTunnel);
                                    vTunnel = new Vector3(prevRoomCenter.Y, newRoomCenter.Y, newRoomCenter.X + 1);
                                    CarveVerticalTunnel(vTunnel);
                                    break;
                                }
                            case 4:
                                {
                                    Vector3 vTunnel = new Vector3(prevRoomCenter.Y, newRoomCenter.Y, newRoomCenter.X);
                                    CarveVerticalTunnel(vTunnel);
                                    vTunnel = new Vector3(prevRoomCenter.Y, newRoomCenter.Y, newRoomCenter.X - 1);
                                    CarveVerticalTunnel(vTunnel);
                                    Vector3 hTunnel = new Vector3(prevRoomCenter.X, newRoomCenter.X, prevRoomCenter.Y);
                                    CarveHorizontalTunnel(hTunnel);
                                    hTunnel = new Vector3(prevRoomCenter.X, newRoomCenter.X, prevRoomCenter.Y + 1);
                                    CarveHorizontalTunnel(hTunnel);
                                    break;
                                }
                            case 5:
                                {
                                    Vector3 hTunnel = new Vector3(prevRoomCenter.X, newRoomCenter.X, prevRoomCenter.Y);
                                    CarveHorizontalTunnel(hTunnel);
                                    hTunnel = new Vector3(prevRoomCenter.X, newRoomCenter.X, prevRoomCenter.Y - 1);
                                    CarveHorizontalTunnel(hTunnel);
                                    hTunnel = new Vector3(prevRoomCenter.X, newRoomCenter.X, prevRoomCenter.Y + 1);
                                    CarveHorizontalTunnel(hTunnel);
                                    Vector3 vTunnel = new Vector3(prevRoomCenter.Y, newRoomCenter.Y, newRoomCenter.X);
                                    CarveVerticalTunnel(vTunnel);
                                    vTunnel = new Vector3(prevRoomCenter.Y, newRoomCenter.Y, newRoomCenter.X + 1);
                                    CarveVerticalTunnel(vTunnel);
                                    vTunnel = new Vector3(prevRoomCenter.Y, newRoomCenter.Y, newRoomCenter.X - 1);
                                    CarveVerticalTunnel(vTunnel);
                                    break;
                                }
                        }
                        #endregion
                    }

                    // TODO - Extrapolate methods as our "stuff" generation becomes more complex
                    // Add stuff to each room
                    TileMap[ChooseRandomRoomCoordiantes(r)].Objects.Add(GenerateGem());
                    TileMap[ChooseRandomRoomCoordiantes(r)].Objects.Add(GenerateMonster(level));
                    Entity item = GenerateItem();
                    item.IsVisible = true;
                    TileMap[ChooseRandomRoomCoordiantes(r)].Objects.Add(item);


                    // Finally, append the new room to the list
                    rooms.Add(newRoom);
                }
            }

            //
            //  The Last Room
            //  Now is the time to add some stuff specifically for the last room generated.
            //
            Vector2 lastRoomCenter = CenterOf(rooms[rooms.Count - 1]);
            Tile lastRoomCenterTile = TileMap[lastRoomCenter];
            lastRoomCenterTile.AddEnvironmentalEntity(DownStairs.CreateStairs());

            Console.WriteLine("Generated " + numberOfRooms + " rooms.");
        }

        /// <summary>
        /// Blocks all the tiles in the map, essentially making them walls.
        /// </summary>
        internal override void BlockAll()
        {
            foreach (Tile t in TileMap.Values)
            {
                t.IsBlocking = true;
                t.Color = WallColor;
            }
        }

        /// <summary>
        /// Carves out a room, setting the interior to just floor.
        /// </summary>
        /// <param name="roomRectangle">A vector storing the X, Y, Width, and Height options for the room.</param>
        internal override void CarveOutRoom(Vector4 roomRectangle)
        {
            int x = (int)roomRectangle.X;
            int y = (int)roomRectangle.Y;
            int width = (int)roomRectangle.Z;
            int height = (int)roomRectangle.W;

            for (int r = x + 1; r < x + width; r++)
            {
                for (int c = y + 1; c < y + height; c++)
                {
                    Vector2 tileCoord = new Vector2(r, c);
                    Tile t = TileMap[tileCoord];
                    t.IsBlocking = false;
                    if (t.Color == WallColor)
                    {
                        t.Color = FloorColor;
                    }
                }
            }
        }

        /// <summary>
        /// Carves out a 1-Tile thick horizontal tunnel
        /// </summary>
        /// <param name="tunnel">A Vector3 with the StartX, EndX, and Y parameters.</param>
        internal void CarveHorizontalTunnel(Vector3 tunnel)
        {
            int startX, endX;
            if (tunnel.X >= tunnel.Y)
            {
                startX = (int)tunnel.Y;
                endX = (int)tunnel.X;
            }
            else
            {
                startX = (int)tunnel.X;
                endX = (int)tunnel.Y;
            }

            int y = (int)tunnel.Z;

            for (int r = startX; r <= endX; r++)
            {
                Vector2 tileCoord = new Vector2(r, y);
                Tile t = TileMap[tileCoord];
                t.IsBlocking = false;
                if (t.Color == WallColor)
                {
                    t.Color = FloorColor;
                }
            }
        }

        /// <summary>
        /// Carves out a 1-Tile thick vertical tunnel.
        /// </summary>
        /// <param name="tunnel">A Vector3 with the StartY, EndY, and X parameters.</param>
        internal void CarveVerticalTunnel(Vector3 tunnel)
        {
            int startY, endY;
            if (tunnel.X >= tunnel.Y)
            {
                startY = (int)tunnel.Y;
                endY = (int)tunnel.X;
            }
            else
            {
                startY = (int)tunnel.X;
                endY = (int)tunnel.Y;
            }

            int x = (int)tunnel.Z;

            for (int c = startY; c <= endY; c++)
            {
                Vector2 tileCoord = new Vector2(x, c);
                Tile t = TileMap[tileCoord];
                t.IsBlocking = false;
                if (t.Color == WallColor)
                {
                    t.Color = FloorColor;
                }
            }
        }

        #endregion

        #region Entity Choosers

        internal RandomStringChooser MoneyChooser;

        internal RandomStringChooser MonsterChooser;

        internal RandomStringChooser ItemChooser;

        #endregion

        #region Properties

        private List<Vector4> rooms { get; set; }

        private int numberOfRooms { get { return rooms.Count; } }

        private Color WallColor { get { return WallColorChooser.Choose(); } }
        private RandomChooser<Color> WallColorChooser = new RandomChooser<Color>(TileEngine.WallColorPallete);

        private Color FloorColor { get { return FloorColorChooser.Choose(); } }
        private RandomChooser<Color> FloorColorChooser = new RandomChooser<Color>(TileEngine.FloorColorPallete);

        private const int MAX_ROOM_SIZE = 16;
        private const int MIN_ROOM_SIZE = 5;
        private const int MAX_ROOMS = 24;

        #endregion
    }
}
